Webhooks
Overview
When a payment is successfully processed for a MiniStore order, Giftme sends a webhook notification to the MiniStore's configured webhook_url. This allows your system to be notified in real-time when payments are completed.
Webhook Configuration
To receive webhooks, configure the following in your MiniStore settings:
webhook_url- The HTTPS endpoint where webhooks will be sentapi_secret- Your secret key used to verify webhook signatures
Webhook Request
HTTP Method
POST
Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Signature | HMAC-SHA256 signature of the payload |
Request Body
{
"transaction_id": "MINISTORE-ABC1234567",
"original_order_id": "your-order-id-123",
"external_reference": "your-external-ref",
"amount": 1500.00,
"currency": "JMD",
"payment_method_type": "split",
"sources": [
{
"source_type": "gift_card",
"source_id": "123",
"amount": 500.00
},
{
"source_type": "smart_card",
"source_id": "456",
"amount": 1000.00
}
],
"status": "completed",
"mode": "live",
"created_at": "2025-12-17T15:30:00+00:00",
"updated_at": "2025-12-17T15:30:05+00:00"
}
Payload Fields
| Field | Type | Description |
|---|---|---|
transaction_id | string | Giftme's unique transaction ID (format: MINISTORE-XXXXXXXXXX) |
original_order_id | string | The order ID you provided in the order token |
external_reference | string|null | External reference you provided (if any) |
amount | number | Order amount (excluding service fees) |
currency | string | Currency code (e.g., JMD, USD) |
payment_method_type | string | Always split for multi-source payments |
sources | array | List of payment sources used |
sources[].source_type | string | Type of payment source (see below) |
sources[].source_id | string | ID of the payment source |
sources[].amount | number | Amount charged to this source |
status | string | Transaction status (completed, failed, pending) |
mode | string | live or sandbox |
created_at | string | ISO 8601 timestamp when transaction was created |
updated_at | string | ISO 8601 timestamp when transaction was last updated |
Source Types
| Source Type | Description |
|---|---|
gift_card | Merchant-specific gift card |
smart_card | Giftme Smart Card |
expense_card | Giftme Expense Card |
payment_method | Bank card (Visa/Mastercard) |
giftme_balance | User's main Giftme wallet balance |
sandbox_card | Test card (sandbox mode only) |
sandbox_wallet | Test wallet (sandbox mode only) |
Signature Verification
Always verify the webhook signature to ensure the request is authentic.
How to Verify
- Get the raw JSON payload from the request body
- Compute HMAC-SHA256 of the payload using your
api_secret - Compare with the
X-Signatureheader
PHP Example
<?php
function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = hash_hmac('sha256', $payload, $secret);
return hash_equals($expectedSignature, $signature);
}
// In your webhook handler:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$secret = 'your-api-secret';
if (!verifyWebhookSignature($payload, $signature, $secret)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($payload, true);
// Process the webhook...
Node.js Example
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signature)
);
}
// In your Express handler:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body.toString();
const signature = req.headers['x-signature'];
const secret = 'your-api-secret';
if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const data = JSON.parse(payload);
// Process the webhook...
res.status(200).send('OK');
});
Python Example
import hmac
import hashlib
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected_signature = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
# In your Flask handler:
@app.route('/webhook', methods=['POST'])
def handle_webhook():
payload = request.get_data()
signature = request.headers.get('X-Signature', '')
secret = 'your-api-secret'
if not verify_webhook_signature(payload, signature, secret):
return 'Invalid signature', 401
data = request.get_json()
# Process the webhook...
return 'OK', 200
Response
Your webhook endpoint should respond with:
- HTTP 2xx (200-299) to indicate successful receipt
- Any other status code will be logged as a failed delivery
Timeout
Giftme waits 5 seconds for your webhook response. Ensure your endpoint responds quickly. If you need to perform long-running operations, acknowledge the webhook first and process asynchronously.
Retry Policy
Currently, webhooks are not automatically retried on failure. Failed webhook deliveries are logged and can be viewed in the webhook logs.
Best Practices
- Always verify signatures - Never trust webhook data without verification
- Respond quickly - Return 200 OK immediately, process async if needed
- Handle duplicates - Use
transaction_idto detect and ignore duplicate deliveries - Use HTTPS - Only configure HTTPS webhook URLs
- Log everything - Keep logs of received webhooks for debugging
Example Webhook Payloads
Successful Live Payment
{
"transaction_id": "MINISTORE-A1B2C3D4E5",
"original_order_id": "ORDER-2024-001",
"external_reference": "POS-TXN-789",
"amount": 2500.00,
"currency": "JMD",
"payment_method_type": "split",
"sources": [
{
"source_type": "gift_card",
"source_id": "gc_12345",
"amount": 1000.00
},
{
"source_type": "payment_method",
"source_id": "pm_67890",
"amount": 1500.00
}
],
"status": "completed",
"mode": "live",
"created_at": "2025-12-17T10:30:00-05:00",
"updated_at": "2025-12-17T10:30:02-05:00"
}
Sandbox Test Payment
{
"transaction_id": "MINISTORE-TEST123456",
"original_order_id": "test-order-001",
"external_reference": null,
"amount": 500.00,
"currency": "JMD",
"payment_method_type": "split",
"sources": [
{
"source_type": "sandbox_card",
"source_id": "sandbox_card",
"amount": 500.00
}
],
"status": "completed",
"mode": "sandbox",
"created_at": "2025-12-17T10:00:00-05:00",
"updated_at": "2025-12-17T10:00:01-05:00"
}